package br.odb.bzk3.android;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;

import android.app.Activity;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.opengl.GLSurfaceView;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import br.odb.libscene.Actor;
import br.odb.libscene.ActorConstants;
import br.odb.libscene.Sector;
import br.odb.libscene.Sector.InvalidSlotException;
import br.odb.utils.math.Vec2;
import br.odb.utils.math.Vec3;
import bzk3.geometry.GLES1Triangle;
import bzk3.geometry.ObjMesh;

/**
 * @author Daniel "Monty" Monteiro
 * */
public class EngineView extends GLSurfaceView implements Runnable,
		VirtualPadClient, OnClickListener, OnTouchListener {
	// ------------------------------------------------------------------------------------------------------------
	/**
	 * 
	 */
	public static boolean loaded = false;
	/**
	 * 
	 */
	private ObjMesh bullet;
	/**
	 * 
	 */
	private ObjMesh ghost;
	/**
	 * 
	 */
	private GameActor cameraActor;
	/**
	 * 
	 */
	private long lastTapTime;
	/**
	 * 
	 */
	private AudioManager audioManager;
	/**
	 * 
	 */
	private Vec2 heading;
	/**
	 * 
	 */
	private Vec2 initialPosition;
	/**
	 * 
	 */
	private boolean[] keyMap;
	/**
	 * 
	 */
	private ArrayList<Actor> people;
	/**
	 * 
	 */
	private GLESRenderer renderer;
	/**
	 * 
	 */
	private volatile boolean running = true;
	/**
	 * 
	 */
	private GameWorld world;

	public Vec3 accel;
	Vec2 lastMove;
	GLES1Triangle fingerPosition;

	// ------------------------------------------------------------------------------------------------------------
	/**
	 * 
	 * @param context
	 */
	public EngineView(Context context) {
		super(context);

		lastMove = new Vec2();
		people = new ArrayList<Actor>();
		keyMap = new boolean[9];
		audioManager = AudioManager.getInstance();
		renderer = new GLESRenderer();
		accel = renderer.accel;
		setFocusable(true);
		setClickable(true);
		setLongClickable(true);
		setFocusableInTouchMode(true);
		requestFocus();
		setRenderer(renderer);
		setOnTouchListener(this);

	}

	// ------------------------------------------------------------------------------------------------------------
	/**
	 * 
	 * @return
	 */
	public GameWorld getWorld() {
		return world;
	}

	// ------------------------------------------------------------------------------------------------------------
	/**
	 * 
	 */
	@Override
	public void handleKeys(boolean[] keymap) {
		if (keymap[VirtualPad.KB_UP]) {

			cameraActor.consume(ActorConstants.MOVE_N);
		}

		if (keymap[VirtualPad.KB_DOWN]) {

			cameraActor.consume(ActorConstants.MOVE_S);
		}

		if (keymap[VirtualPad.KB_LEFT]) {

			cameraActor.consume( ActorConstants.TURN_L );
			renderer.setAngle( cameraActor.getAngleXZ() );
		}

		if (keymap[VirtualPad.KB_RIGHT]) {

			cameraActor.consume( ActorConstants.TURN_R );
			renderer.setAngle( cameraActor.getAngleXZ() );
		}

		if (keymap[VirtualPad.KB_FIRE3]) {
			cameraActor.consume(ActorConstants.MOVE_UP);

		}

		if (keymap[VirtualPad.KB_FIRE2]) {
			cameraActor.consume(ActorConstants.MOVE_DOWN);
		}

		if (keymap[VirtualPad.KB_FIRE]) {
			cameraActor.consume(ActorConstants.FIRE);
		}
	}

	public static final float normalizeX(byte x) {
		return -0.5f
				+ (((x + (-Byte.MIN_VALUE)) * 569) / (Byte.MAX_VALUE - Byte.MIN_VALUE))
				/ (569.0f / 1.2f);

	}

	public static final float normalizeY(byte y) {
		return 0.5f - (((y + (-Byte.MIN_VALUE)) * 270) / (Byte.MAX_VALUE - Byte.MIN_VALUE)) / 270.0f;
	}

	// /TODO: CORTAR ISSO AQUI
	public static final GLES1Triangle[] loadGraphic(InputStream is)
			throws IOException {
		GLES1Triangle[] toReturn;
		int entries = is.read();
		toReturn = new GLES1Triangle[entries];
		byte[] bytes = new byte[10];
		GLES1Triangle t;
		int range = Byte.MAX_VALUE - Byte.MIN_VALUE;
		for (int c = 0; c < entries; ++c) {

			is.read(bytes);

			t = new GLES1Triangle();

			t.a = (bytes[0] + (-Byte.MIN_VALUE));
			t.r = (bytes[1] + (-Byte.MIN_VALUE));
			t.g = (bytes[2] + (-Byte.MIN_VALUE));
			t.b = (bytes[3] + (-Byte.MIN_VALUE));

			t.x0 = normalizeX(bytes[4]);
			t.y0 = normalizeY(bytes[5]);

			t.x1 = normalizeX(bytes[6]);
			t.y1 = normalizeY(bytes[7]);

			t.x2 = normalizeX(bytes[8]);
			t.y2 = normalizeY(bytes[9]);
			t.flatten();
			t.flushToGLES();
			toReturn[c] = t;
		}

		return toReturn;
	}

	// ------------------------------------------------------------------------------------------------------------
	/**
	 * 
	 * @param data
	 * @param detail
	 * @param detailMaterials
	 */
	public void load(InputStream data, InputStream detail,
			InputStream detailMaterials) {
		// <android stuff>
		Thread monitorThread = new Thread(this);
		monitorThread.setPriority(Thread.MAX_PRIORITY);
		monitorThread.start();
		// </android stuff>
		// <init>

		try {

			renderer.addGeometryToScreen(loadGraphic(getResources()
					.openRawResource(R.raw.riffle)));

			world = new GameWorld(data, detail, detailMaterials, people);
			renderer.setWorld(world);

			// fingerPosition = new Triangle();
			// fingerPosition.x0 = normalizeX( (byte) 0 );
			// fingerPosition.x1 = normalizeX( (byte) -5 );
			// fingerPosition.x2 = normalizeX( (byte) 5 );
			// fingerPosition.y0 = normalizeY( (byte) -5 );
			// fingerPosition.y1 = normalizeY( (byte) 5 );
			// fingerPosition.y2 = normalizeY( (byte) 5 );
			// fingerPosition.a = ( 127 + (-Byte.MIN_VALUE));
			// fingerPosition.r = ( 127 + (-Byte.MIN_VALUE));
			// fingerPosition.g = ( 0 + (-Byte.MIN_VALUE));
			// fingerPosition.b = ( 0 + (-Byte.MIN_VALUE));
			// fingerPosition.flatten();
			// fingerPosition.flushToGLES();
			// renderer.addGeometryToScreen( fingerPosition );

		} catch (IOException e) {
			Log.d("bzk3", "deu uma porção de erros!");
		}

		// </init>
	}

	// ------------------------------------------------------------------------------------------------------------
	/**
	 * 
	 * @param actor
	 */
	private void loadBulletMeshFor(GameActor actor) {
		ObjMesh obj = new ObjMesh();
		obj.internalize(getResources().openRawResource(R.raw.torpedo), null);
		actor.setMesh(obj);
		bullet = new ObjMesh();

	}

	// ------------------------------------------------------------------------------------------------------------
	/**
	 * 
	 */
	public void loadComplete() {

		world.consolidate();
		GameActor actor;
		renderer.setWorld(world);
		int visibleSectors = 0;

		for (int c = 0; c < people.size(); c++) {
			actor = (GameActor) people.get(c);
			placeActor(actor, actor.currentSector);

			if (!actor.playable) {

				loadGhostMeshFor(actor);

			}

			renderer.addToScene(actor);
		}

		for (int c = 0; c < world.getTotalSectors(); c++)
			if (!world.getSector(c).isMaster()) {
				visibleSectors++;
				renderer.addToScene((GameSector) world.getSector(c));
			}

		cameraActor = (GameActor) people.get(0);
		cameraActor.playable = true;
		audioManager.setListener(cameraActor);

		renderer.setCurrentCamera(cameraActor);
		renderer.initBuffers(visibleSectors);

		loaded = true;

		Log.d("bzk3", "load complete complete with " + world.getTotalSectors()
				+ " sectors ");
		System.gc();
	}

	// ------------------------------------------------------------------------------------------------------------
	/**
	 * 
	 * @param actor
	 */
	private void loadGhostMeshFor(GameActor actor) {
		ObjMesh obj = new ObjMesh();
		obj.internalize(getResources().openRawResource(R.raw.ghost),
				getResources().openRawResource(R.raw.ghost_mtl));
		actor.setMesh(obj);
		obj.moveTo(actor.getPosition());
	}

	// ------------------------------------------------------------------------------------------------------------
	/**
	 * 
	 */
	@Override
	public void onClick(View v) {
		int id = -1;

		switch (v.getId()) {

		case R.id.btnTurnLeft:
			id = VirtualPad.KB_LEFT;
			break;
		case R.id.btnMoveAhead:
			id = VirtualPad.KB_UP;
			break;
		case R.id.btnTurnRight:
			id = VirtualPad.KB_RIGHT;
			break;
		case R.id.btnMoveBack:
			id = VirtualPad.KB_DOWN;
			break;
		case R.id.btnMoveUp:
			id = VirtualPad.KB_FIRE;
			break;
		case R.id.btnMoveDown:
			id = VirtualPad.KB_FIRE2;
			break;
		case R.id.btnMoveLeft:
			id = VirtualPad.KB_FIRE3;
			break;
		case R.id.btnMoveRight:
			id = VirtualPad.KB_FIRE4;
			break;
		default:
			id = -1;
		}

		if (id != -1) {
			keyMap[id] = true;
			handleKeys(keyMap);
			keyMap[id] = false;
		}

	}

	// ------------------------------------------------------------------------------------------------------------
	/**
	 * 
	 */
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {

		if (keyCode == KeyEvent.KEYCODE_W
				|| keyCode == KeyEvent.KEYCODE_DPAD_UP) {
			keyMap[VirtualPad.KB_UP] = true;
		}
		if (keyCode == KeyEvent.KEYCODE_S
				|| keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
			keyMap[VirtualPad.KB_DOWN] = true;
		}
		if (keyCode == KeyEvent.KEYCODE_Q
				|| keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
			keyMap[VirtualPad.KB_LEFT] = true;
		}
		if (keyCode == KeyEvent.KEYCODE_E
				|| keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
			keyMap[VirtualPad.KB_RIGHT] = true;
		}
		if (keyCode == KeyEvent.KEYCODE_O) {
			keyMap[VirtualPad.KB_FIRE2] = true;
		}
		if (keyCode == KeyEvent.KEYCODE_L) {
			keyMap[VirtualPad.KB_FIRE3] = true;
		}
		if (keyCode == KeyEvent.KEYCODE_ENTER
				|| keyCode == KeyEvent.KEYCODE_MENU) {
			keyMap[VirtualPad.KB_FIRE] = true;
		}

		if (keyCode == KeyEvent.KEYCODE_BACK)
			System.exit(0);

		handleKeys(keyMap);

		return true;
	}

	// ------------------------------------------------------------------------------------------------------------
	/**
	 * 
	 */
	@Override
	public boolean onKeyUp(int keyCode, KeyEvent event) {

		if (keyCode == KeyEvent.KEYCODE_W
				|| keyCode == KeyEvent.KEYCODE_DPAD_UP) {
			keyMap[VirtualPad.KB_UP] = false;
		}
		if (keyCode == KeyEvent.KEYCODE_S
				|| keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
			keyMap[VirtualPad.KB_DOWN] = false;
		}
		if (keyCode == KeyEvent.KEYCODE_Q
				|| keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
			keyMap[VirtualPad.KB_LEFT] = false;
		}
		if (keyCode == KeyEvent.KEYCODE_E
				|| keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
			keyMap[VirtualPad.KB_RIGHT] = false;
		}
		if (keyCode == KeyEvent.KEYCODE_O) {
			keyMap[VirtualPad.KB_FIRE2] = false;
		}
		if (keyCode == KeyEvent.KEYCODE_L) {
			keyMap[VirtualPad.KB_FIRE3] = false;
		}
		if (keyCode == KeyEvent.KEYCODE_ENTER
				|| keyCode == KeyEvent.KEYCODE_MENU) {
			keyMap[VirtualPad.KB_FIRE] = false;
		}

		if (keyCode == KeyEvent.KEYCODE_BACK)
			((Activity) getContext()).finish();

		handleKeys(keyMap);
		return true;
	}

	// ------------------------------------------------------------------------------------------------------------
	/**
	 * 
	 */
	@Override
	public boolean onTouch(View v, MotionEvent event) {

		switch (event.getAction()) {

		case MotionEvent.ACTION_DOWN:
			initialPosition = new Vec2(event.getX(), event.getY());
			break;

		case MotionEvent.ACTION_UP:

			initialPosition = null;
			heading = null;
			long thisTapTime = System.currentTimeMillis();

			if (thisTapTime - lastTapTime < 2000) {
				Log.d("bzk3", "shoot!");
				cameraActor.consume(ActorConstants.FIRE);
				lastTapTime = 0;
			} else {
				lastTapTime = thisTapTime;

				if (event.getY() > (9 * getHeight()) / 10.0f) {
					if (event.getX() > getWidth() / 2)
						cameraActor.consume(ActorConstants.MOVE_UP);
					else
						cameraActor.consume(ActorConstants.MOVE_DOWN);
				}
			}

			break;
		case MotionEvent.ACTION_MOVE: {

			lastTapTime = 0;
			lastMove.set(event.getX(), event.getY());
			updateMovement();
		}
			break;

		}

		return true;
	}

	// ------------------------------------------------------------------------------------------------------------
	/**
	 * 
	 */
	private void updateMovement() {

		if (initialPosition == null)
			return;

		heading = lastMove.sub(initialPosition);

		if (heading.x < -getWidth() / 16) {

			initialPosition.set(lastMove);
			cameraActor.consume( ActorConstants.TURN_L );
			renderer.setAngle( cameraActor.getAngleXZ() );
		}

		if (heading.x > getWidth() / 16) {

			initialPosition.set(lastMove);
			cameraActor.consume( ActorConstants.TURN_R );
			renderer.setAngle( cameraActor.getAngleXZ() );
		}

		if (heading.y < -getHeight() / 10) {

			cameraActor.consume(ActorConstants.MOVE_N);
		}

		if (heading.y > getHeight() / 10) {

			cameraActor.consume(ActorConstants.MOVE_S);
		}
	}

	// ------------------------------------------------------------------------------------------------------------
	/**
	 * 
	 * @param actor
	 * @param sectorId
	 */
	public void placeActor(GameActor actor, int sectorId) {

		GameSector sector = (GameSector) world.getSector(sectorId);
		actor.moveTo(sector.getCenter());
		actor.setCurrentSector(sectorId);
		sector.onSectorEnteredBy(actor);
	}

	// ------------------------------------------------------------------------------------------------------------
	private void doGravity(GameActor actor) {
		GameSector sector = (GameSector) world.getSector(actor
				.getCurrentSector());
		GameSector candidate = sector;
		GameSector groundSector = sector;

		try {


			while (groundSector != null
					&& (groundSector.getLink(Sector.FACE_FLOOR) != Sector.NO_LINK)) {

				candidate = (GameSector) world.getSector(groundSector
						.getLink(Sector.FACE_FLOOR));

				if (candidate.isMaster()) {
					continue;
				}

				groundSector = candidate;

			}


			while ((groundSector.getY0() + actor.getDY()) > (actor
					.getPosition().getY())) {
				actor.consume(ActorConstants.MOVE_UP);
			}

			while ((groundSector.getY0() + actor.getDY()) < (actor
					.getPosition().getY())) {
				
				actor.consume(ActorConstants.MOVE_DOWN);
			}

		} catch (InvalidSlotException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	// ------------------------------------------------------------------------------------------------------------
	/**
	 * 
	 */
	@Override
	public void run() {

		ArrayList<Actor> toBeAdded = new ArrayList<Actor>();
		Actor sons;
		boolean actorHasChangedSector;
		GameSector originalSector;
		GameActor actor;
		

		actorHasChangedSector = true;

		while (running) {

			try {
				Thread.sleep(10);

				if (!loaded)
					continue;

				// audioManager.update();
				toBeAdded.clear();
				int count = people.size();
				for (int c = 0; c < count; ++c) {

					actor = (GameActor) people.get(c);

					if (!actor.isAlive())
						continue;

					actor.tick();

					doGravity(actor);

					originalSector = (GameSector) world.getSector(actor.currentSector);

					findBestFitForActorFromSector( actor, originalSector );
					
					if ( originalSector != world.getSector(actor.currentSector) ) {
						
						actorHasChangedSector = true;
					}

					sons = actor.checkGenerated();

					if (sons != null) {
						toBeAdded.add(sons);
					}
				}

				for (int c = 0; c < toBeAdded.size(); ++c) {

					actor = (GameActor) toBeAdded.get(c);
					actor.setId(people.size() + c);
					loadBulletMeshFor(actor);
					
					try {
						actor.getPosition().setIndex(-2);
					} catch (Exception e) {
						Log.d("bzk3", "e= " + e);
					}
					
					actorHasChangedSector = true;
					people.add(actor);
					renderer.addToScene(actor);
				}

				if (actorHasChangedSector) {
					renderer.needsToResetView();
				}
				actorHasChangedSector = false;

			} catch (Exception e) {
				Log.d("bzk3", "ex:" + e);
			}
		}
	}

	private void findBestFitForActorFromSector(GameActor actor,
			GameSector originalSector) {
		
		int[] neighbours;
		Sector sector = originalSector;
		
		if (!originalSector.contains(actor.getPosition())) {

			neighbours = originalSector.getNeighbours();

			for (int d = 0; d < neighbours.length; ++d) {

				sector = (GameSector) world.getSector(neighbours[d]);

				if (sector.isMaster()) {
					continue;
				}

				if (neighbours[d] != 0 && sector.contains(actor.getPosition()))
					break;

				sector = null;
			}

			if (sector == null) {
				
				int size = world.getTotalSectors();
				for (int d = 1; d < size; ++d) {

					sector = (GameSector) world.getSector(d);

					if (sector.isMaster())
						continue;

					if (sector.contains(actor.getPosition()))
						break;

					sector = null;
				}

			}

			if (sector == null || sector.isMaster()) {

				originalSector.onSectorLeftBy(actor);
				actor.setCurrentSector(originalSector.getId());
				actor.undo();

			} else {

				originalSector.onSectorLeftBy(actor);
				sector.onSectorEnteredBy(actor);
				actor.setCurrentSector(sector.getId());
			}
		}
	}

	// ------------------------------------------------------------------------------------------------------------
	/**
	 * 
	 */
	@Override
	public String toString() {
		return world.toString() + "\n" + renderer.toString();
	}
	// ------------------------------------------------------------------------------------------------------------
}
